#include "StdAfx.h"
#include "GameApp.h"
#include "Resource.h"
#include <stdexcept>
#include <string>
#include "RenderableObject.h"
#include "CubeMesh.h"
#include "TerrainMesh.h"
#include "Timer.h"
#include "GraphicsController.h"
#include "WindowBuilder.h"
#include "D3DInitializer.h"
#include "ThirdPersonCamera.h"
#include "PhysicalObject.h"
#include "GameStateController.h"
#include "InputController.h"
#include "ActionObject.h"
#include "Light.h"
#include "LaserMesh.h"
#include "AnimatedImage.h"
#include "IWFFileMesh.h"
#include <assert.h>


/////////////////////////////////////////////////////////////////////////////

CGameApp::CGameApp(HINSTANCE hInstance, int nCmdShow)
      : m_hInstance(hInstance),
      m_d3dInitializer(),
      m_graphicsController(NULL),
      m_inputController(NULL),
      m_gameStateController(NULL),
      m_timer(new CTimer())
{
   m_d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
   if (m_d3d9 == NULL)
   {
      throw std::logic_error(std::string("Cannot initialize DirectX - check if it's installed"));
   }

   bool windowedMode = true;
   UINT screenWidth = 800;
   UINT screenHeight = 600;

   if (initInstance(windowedMode, screenWidth, screenHeight, nCmdShow) == false)
   {
      throw std::logic_error(
         std::string("Window initialization failed"));
   }

   m_d3dInitializer = new CD3DInitializer(*m_d3d9, m_hWnd);

   CD3DSettings D3DSettings;
   if (windowedMode == false)
   {
      D3DDISPLAYMODE requestedMode;
      requestedMode.Width = screenWidth;
      requestedMode.Height = screenHeight;
      requestedMode.Format = D3DFMT_UNKNOWN;
      requestedMode.RefreshRate = 0;

      D3DSettings = m_d3dInitializer->findBestFullscreenMode(requestedMode);
   }
   else
   {
      D3DSettings = m_d3dInitializer->findBestWindowedMode();
   }

   m_graphicsController = m_d3dInitializer->createDisplay(D3DSettings, m_hWnd);
   if (m_graphicsController == NULL)
   {
      throw std::logic_error(
         std::string("Graphics device initialization failed"));
   }

   m_graphicsController->setTextureDirectoryPath(_T("..\\Data\\"));
   m_inputController = new CInputController(*this, m_hWnd);
 
   buildScene();

   m_gameStateController = new GameStateController(*this, *this, *this, 
                                                   *m_inputController, 
                                                   m_graphicsController->createCamera());
}

/////////////////////////////////////////////////////////////////////////////

CGameApp::~CGameApp(void)
{
   m_renderableObjects.clear();

   if (m_graphicsController != NULL)
   {
      delete m_graphicsController;
      m_graphicsController = NULL;
   }

   for (std::map<std::string, CTerrainMesh*>::iterator it = m_terrain.begin(); it != m_terrain.end(); ++it)
   {
      delete it->second;
   }
   m_terrain.clear();

   for (std::map<std::string, IMesh*>::iterator it = m_mesh.begin(); it != m_mesh.end(); ++it)
   {
      delete it->second;
   }
   m_mesh.clear();

   for (std::map<std::string, AnimatedImage*>::iterator it = m_image.begin(); it != m_image.end(); ++it)
   {
      delete it->second;
   }
   m_image.clear();

   delete m_gameStateController;
   m_gameStateController = NULL;

   if (m_inputController != NULL)
   {
      delete m_inputController;
      m_inputController = NULL;
   }

   delete m_timer;
   m_timer = NULL;

   if (m_d3d9 != NULL)
   {
      m_d3d9->Release();
      m_d3d9 = NULL;
   }
}

/////////////////////////////////////////////////////////////////////////////

bool CGameApp::initInstance(bool windowedMode, UINT screenWidth, UINT screenHeight, int nCmdShow)
{
   CWindowBuilder wndBuilder;

   if (windowedMode)
   {
      m_hWnd = wndBuilder.createWindowedModeWindow(m_hInstance, screenWidth, screenHeight, this);
   }
   else
   {
      m_hWnd = wndBuilder.createFullScreenWindow(m_hInstance, screenWidth, screenHeight, this);
   }
   
   if (!m_hWnd)
   {
      return false;
   }

   ShowWindow(m_hWnd, nCmdShow);
   UpdateWindow(m_hWnd);

   return true;
}

/////////////////////////////////////////////////////////////////////////////

void CGameApp::buildScene(void)
{
   // create a terrain mesh
   D3DMATERIAL9 terrainMat;
   ZeroMemory(&terrainMat.Ambient, sizeof(terrainMat.Ambient));
   ZeroMemory(&terrainMat.Specular, sizeof(terrainMat.Specular));
   ZeroMemory(&terrainMat.Emissive, sizeof(terrainMat.Emissive));
   terrainMat.Diffuse.r = 1.0f; terrainMat.Diffuse.g = 0.8f; terrainMat.Diffuse.b = 0.6f;

   m_terrain.insert(std::make_pair("difficult",
                                   new CTerrainMesh(*m_graphicsController,
                                          _T("..\\Data\\HeightMap.raw"),
                                          257, 257,
                                          _T("..\\Data\\Base_Texture_BigPoint.jpg"),
                                          _T("..\\Data\\Detail_Texture.jpg"))));

   m_terrain.insert(std::make_pair("easy",
                                   new CTerrainMesh(*m_graphicsController,
                                          _T("..\\Data\\EasyTrack_HeightMap.raw"),
                                          257, 257,
                                          _T("..\\Data\\Base_Texture_EasyTrack.jpg"),
                                          _T("..\\Data\\Detail_Texture.jpg"))));
 
   // set up a light
   CLight& light = m_graphicsController->createDynamicLight(D3DLIGHT_DIRECTIONAL);
   light.setDiffuseColor(1, 1, 1);
   light.setDirection(D3DXVECTOR3(0, -0.707f, 0.707f));
   light.enable(true);

   // set up the skybox
   m_graphicsController->setSkyBox(_T("..\\Data\\SkyBox_Top.jpg"),
                                   _T("..\\Data\\SkyBox_Bottom.jpg"),
                                   _T("..\\Data\\SkyBox_Front.jpg"),
                                   _T("..\\Data\\SkyBox_Back.jpg"),
                                   _T("..\\Data\\SkyBox_Left.jpg"),
                                   _T("..\\Data\\SkyBox_Right.jpg"));

   // prepare meshes
   m_mesh.insert(std::make_pair("laser", new LaserMesh(*m_graphicsController)));
   m_mesh.insert(std::make_pair("car", new IWFFileMesh(*m_graphicsController, _T("..\\Data\\Car.iwf"))));
   m_mesh.insert(std::make_pair("pole", new IWFFileMesh(*m_graphicsController, _T("..\\Data\\pole.iwf"))));

   // load images
   m_image.insert(std::make_pair("polePositionLight", 
                                 new AnimatedImage(*m_graphicsController,
                                                  _T("..\\Data\\PolePositionLights.bmp"), 
                                                   3, 40, 126, true)));

   m_image.insert(std::make_pair("introScreen", 
                                 new AnimatedImage(*m_graphicsController,
                                                  _T("..\\Data\\Intro.bmp"), 
                                                   1, 800, 600)));

   m_image.insert(std::make_pair("mainMenuScreen", 
                                 new AnimatedImage(*m_graphicsController,
                                                  _T("..\\Data\\MainMenu.bmp"), 
                                                   1, 800, 600)));

   m_image.insert(std::make_pair("selectionHighlight", 
                                 new AnimatedImage(*m_graphicsController,
                                                  _T("..\\Data\\SelectionHighlight.bmp"), 
                                                   1, 512, 128, true)));

   m_image.insert(std::make_pair("victory", 
                                 new AnimatedImage(*m_graphicsController,
                                                  _T("..\\Data\\owned.bmp"), 
                                                   1, 256, 64, true)));

   m_image.insert(std::make_pair("defeat", 
                                 new AnimatedImage(*m_graphicsController,
                                                  _T("..\\Data\\looser.bmp"), 
                                                   1, 256, 64, true)));
}

/////////////////////////////////////////////////////////////////////////////

IScenery& CGameApp::getTerrainScenery(const std::string& trackName)
{
   return *(m_terrain[trackName]);
}

/////////////////////////////////////////////////////////////////////////////

IRenderableObject& CGameApp::getTerrainRenderable(const std::string& trackName)
{
   return *(m_terrain[trackName]);
}

/////////////////////////////////////////////////////////////////////////////

void CGameApp::beginGame(void)
{
   MSG msg;

   while (1) 
   {
      if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 
      {
         if (msg.message == WM_QUIT) break;
         TranslateMessage(&msg);
         DispatchMessage (&msg);
      } 
      else
      {
         advanceGameState();
      }
   }
}

/////////////////////////////////////////////////////////////////////////////

void CGameApp::closeApp(void)
{
   PostQuitMessage(0);
}

/////////////////////////////////////////////////////////////////////////////

void CGameApp::advanceGameState(void)
{
   m_timer->tick();

   m_inputController->processInput(m_timer->getTimeElapsed());

   m_gameStateController->update(m_timer->getTimeElapsed());

   // render the scene
   if (m_graphicsController->isGraphicsSystemReady() == true)
   {
      m_graphicsController->render(m_renderableObjects);
      m_graphicsController->present();
   }
}

/////////////////////////////////////////////////////////////////////////////

void CGameApp::startRendering(IRenderableObject& renderable)
{
   m_renderableObjects.push_back(&renderable);
}

/////////////////////////////////////////////////////////////////////////////

void CGameApp::stopRendering(IRenderableObject& renderable)
{
   std::vector<IRenderableObject*>::iterator it = m_renderableObjects.begin();

   while ( (it != m_renderableObjects.end()) && ((void*)(*it) != (void*)(&renderable)) ) 
   {
      ++it;
   }

   if (it != m_renderableObjects.end())
   {
      m_renderableObjects.erase(it);
   }
}

/////////////////////////////////////////////////////////////////////////////

LRESULT CGameApp::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   int wmId, wmEvent;

   switch (message)
   {
      case WM_COMMAND:
      {
         wmId    = LOWORD(wParam);
         wmEvent = HIWORD(wParam);
         // Parse the menu selections:
         switch (wmId)
         {
         case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
         default:
            return DefWindowProc(hWnd, message, wParam, lParam);
         }
         break;
      }

      case WM_SIZE:
      {
         UINT screenWidth  = LOWORD( lParam );
         UINT screenHeight = HIWORD( lParam );

         if (m_graphicsController)
         {
            m_graphicsController->resizeViewport(screenWidth, screenHeight);
         }

         break;
      }

      case WM_DESTROY:
      {
         closeApp();
         break;
      }

      default:
      {
			return DefWindowProc(hWnd, message, wParam, lParam);
      }
   }
   return 0;
}

/////////////////////////////////////////////////////////////////////////////
